<?php
/**
 * Created by PhpStorm.
 * User: guokunsheng
 * Date: 2018/6/6
 * Time: 17:35
 */

namespace app\api\controller\v21;

use app\api\controller\v13\Auth2;
use app\common\cachemodel\AppConfig;
use app\common\cachemodel\AppLoginLog;
use app\common\cachemodel\AppQq;
use app\common\cachemodel\AppWeibo;
use app\common\cachemodel\MiniAppUser;
use app\common\cachemodel\Recode;
use app\common\cachemodel\Wechat;
use app\common\cachemodel\ZlAppleidUser;
use app\common\cachemodel\ZlRecordShareAdd;
use app\common\cachemodel\ZlUserZx;
use app\common\lib\Alidayu;
use app\common\lib\exception\ApiException;
use app\common\lib\IAuth;
use app\common\lib\Aes;
use app\common\lib\SensorsData;
use app\common\lib\Util;
use app\common\model\AppTelLogin;
use app\common\model\User;
use app\common\model\UserPush;
use Exception;
use GuzzleHttp\Exception\GuzzleException;
use think\Cache;
use \think\Db;
use think\Log;
use think\response\Json;

/**
 * @title 登录相关接口
 * @desc 账号密码登录 <br/>手机号验证码登录<br/>微信登录<br/>QQ登录<br/>微博登录<br/>
 * @version 2.5.0
 * @url /api/v2/login/...
 * @readme /doc/api/login/login.md
 */
class Login extends Auth2
{
    protected $restActionList = ['qq', 'weibo'];
    protected $loginType = null;
    protected $msg = '';

    /**
     * 三方登录接口
     * @return Json
     * @throws ApiException
     * @throws GuzzleException
     */
    public function OuterLogin()
    {
        #神策打点 5   登录成功
        $sensorsTrackData = [
            'is_register' => false,
            'login_type' => '手机号',// 手机号、微信、QQ、微博、AppleID、手机号一键登录
            'is_temporary' => false,// 三方登录时，需要设置这个属性，是否为使用临时手机号验证
            'is_success' => true,
            'failed_reson' => '',
        ];
        $distinctId = $this->request->header('distinct-id', '');
        $param = input('param.param');
        $param = $param ? json_decode($param, true) : [];
        $type = input('param.type/d');
        $this->loginType = $type;
        switch ($type)
        {
            // 微信登录
            case 1:
                $sensorsTrackData['login_type'] = '微信';
                if ($this->headers['app-type'] === 'ios')
                {
                    $wechat_user = $param;
                } else
                {
                    $wechat_user = (new Wechat())->WechatInfo($param);
                }
                $res = $this->wechat($wechat_user);
                break;
            // QQ登录
            case  2:
                $sensorsTrackData['login_type'] = 'QQ';
                $res = $this->qq($param);
                break;
            // 微博登录
            case  3:
                $sensorsTrackData['login_type'] = '微博';
                $res = $this->weibo($param);
                break;
            // AppleID登录
            case  5:
                $sensorsTrackData['login_type'] = 'AppleID';
                $res = $this->appleId($param);
                break;
            default:
                if ($distinctId)
                {
                    $sensorsTrackData['is_success'] = false;
                    $sensorsTrackData['failed_reson'] = '未知的登录方式';
                    SensorsData::track($distinctId, false, 'LoginSuccess', $sensorsTrackData);
                }
                return show(config('code.error'), '未知的登录方式');
        }

        if ($this->msg)
        {
            if ($distinctId)
            {
                $sensorsTrackData['is_success'] = false;
                $sensorsTrackData['failed_reson'] = $this->msg;
                SensorsData::track($distinctId, false, 'LoginSuccess', $sensorsTrackData);
            }
            return show(config('code.error'), $this->msg);
        }

        //记录登录
        $this->LoginLog($res['id'], $type);
        if ($distinctId)
        {
            $sensorsTrackData['is_register'] = $res['is_register'];
            $sensorsTrackData['is_temporary'] = $res['temporary_tel'];
            SensorsData::track($res['id'], true, 'LoginSuccess', $sensorsTrackData);
        }
        #删除打点字段
        unset($res['is_register'], $res['temporary_tel']);

        return show(config('code.success'), '登录成功', $res);
    }

    /**
     * 微信登录
     * @param $wechat_user json 微信用户信息
     * @return array
     * @throws ApiException
     * @throws GuzzleException
     * @throws Exception
     */
    private function wechat($wechat_user)
    {
        $this->msg = '';
        $wechat_user['headimgurl'] = $wechat_user['headimgurl'] ?? '';
        $wechat_user['sex'] = $wechat_user['sex'] ?? ($wechat_user['gender'] ?? 0);

        $WechatModel = new Wechat();
        //验证是否注册过
        $User = (new Wechat())->getWechatInfo($wechat_user['unionid']);
        if ($User)
        {
            return $this->LoginUser($User, $wechat_user);
        }
        //新增用户，qq和user表进行数据添加，检测冷却时间
        $checkResult = Util::registCheckLogoutCD(['unionid' => $wechat_user['unionid']]);
        if (!$checkResult['result'])
        {
            return $this->msg = $checkResult['msg'];
        }
        $res = [];
        Db::startTrans();
        try
        {
            $miniAppInfo = (new MiniAppUser())->where(['mini_type' => 'WEAPP', 'unionid' => $wechat_user['unionid']])->where('status', '<>', 1)->find();
            if ($miniAppInfo)
            {
                if ($miniAppInfo['status'] == 2)
                {
                    return $this->msg = '该账户禁止登陆';
                }
                if ($miniAppInfo['uid'])
                {
                    $uid = $miniAppInfo['uid'];
                } else
                {
                    $res = $this->RegUser($wechat_user, 1, $wechat_user['unionid']);
                    $uid = $res['id'];
                    (new MiniAppUser())->save(['uid' => $uid], ['id' => $miniAppInfo['id']]);
                }
            } else
            {
                $res = $this->RegUser($wechat_user, 1, $wechat_user['unionid']);
                $uid = $res['id'];
            }
            $wechat_insert = [
                'uid' => $uid,
                'nickname' => $wechat_user['nickname'] ?? '匿名用户',
                'sex' => $wechat_user['sex'] ?? 2,// 微信注册用户 性别字段可能不存在
                'city' => $wechat_user['city'] ?? '',
                'province' => $wechat_user['province'] ?? '',
                'country' => $wechat_user['country'] ?? '',
                'headimgurl' => $wechat_user['headimgurl'] ?? '',
                'openid' => $wechat_user['openid'],
                'unionid' => $wechat_user['unionid'],
                'create_time' => $this->request->time(),
                'update_time' => $this->request->time(),
            ];
            //qq表生成数据
            $WechatModel->SaveData($wechat_insert);
            Db::commit();
        }
        catch (Exception $e)
        {
            Log::write('用户注册失败：' . $e->getMessage() . '【' . $e->getFile() . $e->getLine() . '】', 'error');
            Db::rollback();
            $this->msg = '用户注册失败';
            return [];
        }
        return $res;
    }

    /**
     * QQ登录
     * @param $param
     * @return array|Json
     * @throws ApiException
     * @throws GuzzleException
     * @throws Exception
     */
    private function qq($param)
    {
        $this->msg = '';
        $QqModel = new AppQq();
        // 获取用户qq信息
        $qq_user = $QqModel->QqInfo($param);
        // 验证是否注册过
        $User = (new AppQq())->getUserQqInfoFromCache($qq_user['unionid']);
        if ($User)
        {
            return $this->LoginUser($User, $qq_user);
        }
        // 新增用户，qq和user表进行数据添加
        $checkResult = Util::registCheckLogoutCD(['qq_unionid' => $qq_user['unionid']]);
        if (!$checkResult['result'])
        {
            return $this->msg = $checkResult['msg'];
        }
        Db::startTrans();
        try
        {
            $res = $this->RegUser($qq_user, 2, $qq_user['unionid']);
            $qq_insert = [
                'uid' => $res['id'],
                'openid' => $param['openid'],
                'unionid' => $qq_user['unionid'],
                'create_time' => time(),
                'nickname' => $qq_user['nickname'],
                'sex' => $qq_user['sex'],
                'avatar' => $qq_user['figureurl_qq_1'],
            ];
            //qq表生成数据
            (new AppQq())->SaveData($qq_insert);
            Db::commit();
        }
        catch (Exception $e)
        {
            Log::write('用户注册失败：' . $e->getMessage() . '【' . $e->getFile() . $e->getLine() . '】', 'error');
            Db::rollback();
            return $this->msg = '用户注册失败';
        }
        return $res;
    }

    /**
     * 微博登录
     * @param $param
     * @return array|Json
     * @throws ApiException
     * @throws GuzzleException
     * @throws Exception
     */
    private function weibo($param)
    {
        $this->msg = '';
        $WbModel = new AppWeibo();
        // 获取用户weibo信息
        $wb_user = $WbModel->WeiboInfo($param);
        // 验证是否注册过
        $User = (new AppWeibo())->getUserWbInfoFromCache($param['uid']);
        if ($User)
        {
            return $this->LoginUser($User, $wb_user);
        }

        $checkResult = Util::registCheckLogoutCD(['wuid' => $param['uid']]);
        if (!$checkResult['result'])
        {
            return $this->msg = $checkResult['msg'];
        }
        Db::startTrans();
        try
        {
            // 新增用户，qq和user表进行数据添加
            $res = $this->RegUser($wb_user, 3);
            $wb_insert = [
                'uid' => $res['id'],
                'wbuid' => $param['uid'],
                'create_time' => time(),
                'nickname' => $wb_user['name'],
                'sex' => $wb_user['sex'],
                'avatar' => $wb_user['profile_image_url'],
            ];
            //wb表生成数据
            (new AppWeibo())->SaveData($wb_insert);
            Db::commit();
        }
        catch (Exception $e)
        {
            Log::write('用户注册失败：' . $e->getMessage() . '【' . $e->getFile() . $e->getLine() . '】', 'error');
            Db::rollback();
            return $this->msg = '用户注册失败';
        }
        return $res;
    }

    /**
     * AppleId登录
     * @param $param
     * @return array|Json
     * @throws ApiException
     * @throws GuzzleException
     */
    public function appleId($param)
    {
        $this->msg = '';
        $dbModel = new ZlAppleidUser();
        if (!isset($param['apple_id']) || empty($param['apple_id']))
        {
            return $this->msg = '用户登陆失败';
        }
        $param['unionid'] = $param['apple_id'];
        $param['headimgurl'] = $param['avatar'] ?? '';
        $param['nickname'] = $param['name'] ?? '';
        $param['nickname'] = $param['nickname'] ?: '准了' . uniqid();
        $param['sex'] = 0;

        // 验证是否注册过
        $user = $dbModel->getUserInfo($param['apple_id']);
        if ($user)
        {   // 去登陆
            $res = $this->LoginUser($user, $param);
        } else
        {
            // 去注册
            //新增用户，qq和user表进行数据添加
            $checkResult = Util::registCheckLogoutCD(['apple_id' => $param['apple_id']]);
            if (!$checkResult['result'])
            {
                return $this->msg = $checkResult['msg'];
            }

            try
            {
                Db::startTrans();
                $res = $this->RegUser($param, 5, $param['apple_id']);
                $param['uid'] = $res['id'];
                $param['create_time'] = $this->request->time();
                $dbModel->SaveData($param);
                Db::commit();
            }
            catch (Exception $e)
            {
                Log::write($param['apple_id'] . '：苹果用户注册失败：' . $e->getMessage() . '【' . $e->getFile() . $e->getLine() . '】', 'error');
                Db::rollback();
                return $this->msg = '用户注册失败';
            }
        }
        return $res;
    }

    /**
     * 用户注册
     * @param $user_info
     * @param $type 1微信 2QQ 3微博
     * @param string $code 身份标识
     * @return array
     * @throws ApiException
     * @throws GuzzleException
     * @throws Exception
     */
    private function RegUser($user_info, $type, $code = '')
    {
        $this->msg = '';
        $time = $this->request->time();

        $ip = $this->request->ip();

        Db::startTrans();
        try
        {
            $token_info = $this->getUserTokenInfo($user_info);
            // headimgurl可能不存在，此处进行初始化
            $user_info['headimgurl'] = $user_info['headimgurl'] ?? '';
            // sex可能不存在，此处进行初始化
            $user_info['sex'] = $user_info['sex'] ?? 0;

            if ($user_info['headimgurl'])
            {
                $avatarInfo = Util::wxImgUploadOss($user_info['headimgurl'], 'uploads/avatar');
            } else
            {
                $avatarInfo = [
                    'success' => true,
                    'url' => ''
                ];
            }

            if ($avatarInfo['success'] == false)
            {
                Log::write('上传三方头像失败:' . $avatarInfo['message'], 'error');
            }

            $avatar = $avatarInfo['success'] ? $avatarInfo['url'] : $user_info['headimgurl'];

            $user = [
                'uen' => substr(md5(microtime() . random_int(0, 1000000) . uniqid('', false)), 0, 12),
                'nickname' => $user_info['nickname'],
                'avatar' => $avatar,
                'sex' => $user_info['sex'],
                'reg_time' => $time,
                'reg_ip' => $ip,
                'last_login' => $time,
                'last_ip' => $ip,
                'last_active' => $time,
                'is_first_login' => 1,// 首次登陆标记
                'uc_id' => md5($user_info['unionid'] . microtime()),
                'token' => $token_info['token'],
                'token_expire' => $token_info['token_expire'],
                'email' => null,
                'forbidden' => 0,
                'has_self_recode' => 0,
                'channel' => Util::getChannelId()
            ];
            $user['id'] = (new User())->insertGetId($user);
            Cache::set('user_total_amount_' . $user['id'], 0, 86400);   // 消费金额为0
            $msg = Db::table('app_message_center')->field('id')->where('range', 0)->select();
            foreach ($msg as $k => $v)
            {
                Db::table('app_user_msg')->insert(['uid' => $user['id'], 'mid' => $v['id']]);
            }
            Db::commit();

            // 记录注册日志
            Util::logRealTimeAnalyzeWrite(1, [
                'user_uid' => $user['id'],
                'user_name' => $user_info['nickname'],
                'user_sex' => $user_info['sex'],
                'user_reg_type' => $type,//$type 1微信 2QQ 3微博 5appleId
                'user_ip' => $ip
            ]);
        }
        catch (Exception $e)
        {
            Log::write($type . '用户注册失败：' . $e->getMessage() . '【' . $e->getFile() . $e->getLine() . '】', 'error');
            Db::rollback();
            return $this->msg = '用户注册失败';
        }

        if (in_array($type, [1, 2], true))
        {
            $xzNum = (new ZlUserZx())->getZxNum($type, $code);
            if ($xzNum < 1)
            {
                $hasRecord = (new ZlRecordShareAdd())->registAddRecord($avatar, $user_info['nickname'], $user['id'], $type, $code);
                if ($hasRecord)
                {
                    (new User())->save(['has_self_recode' => 1], ['id' => $user['id']]);
                    $user['has_self_recode'] = 1;  // 有档案
                }
            }
        }
        $distinctId = $this->request->header('distinct-id', '');
        if ($distinctId)
        {
            // 神策打点，用户关联
            SensorsData::signup($user['id'], $distinctId);
            // 三方注册账号时，更新用户属性
            $userData = [
                'user_resgistration_time' => date('Y-m-d H:i:s', $user['reg_time']),
                'user_uen' => $user['uen'],
                'user_is_vip' => false,
                'user_vip_level' => 0,
                'user_star_num' => 0
            ];
            SensorsData::profile($user['id'], true, $userData, 'set');
        }

        // 判断用户 APP版本大于等于 2.4.0 赠送 新手礼包
        if (Util::CheckAppVersion('2.4.0') !== '小')
        {   // 不小于 2.4.0
            Util::queuePush('register_package', ['uid' => (int)$user['id']], 0);
        }
        // 记录登陆设备日志
        Util::loginRecordSave((int)$user['id'], $this->loginType, 1, $ip);

        return [
            'id' => (int)$user['id'],
            'has_self_recode' => $user['has_self_recode'],
            'user_access_token' => (new Aes())->encryptWithOpenssl($token_info['token'] . '||' . $user['id']),
            'uc_id' => $user['uc_id'],
            'calendar_birthday_status' => 1,   // 在天象日历中展示生日信息  刚注册 默认开
            'recode_private_status' => 1,   // 在星盘中展示档案详细信息  刚注册 默认开
            'has_bind_tel' => 0, // 是否绑定了手机号 1绑定了 0未绑定
            'temporary_tel' => false,
            'is_register' => true,
            'is_young_model' => 2,  // 1开启青少年模式 2关闭青少年模式
        ];
    }

    /**
     * 用户登录
     * @param $user
     * @param $user_info
     * @return array|Json
     * @throws GuzzleException
     * @throws ApiException
     */
    private function LoginUser($user, $user_info)
    {
        $this->msg = '';
        if (!isset($user['forbidden']) || $user['forbidden'] == 1)
        {
            return show(config('code.error'), '该账号禁止登录');
        }
        $UserModel = new User();
        try
        {
            $uid = (int)$user['uid'];
            $token_info = $this->getUserTokenInfo($user_info);

            if (empty($user['avatar']) || preg_match('/^http(s?):\/\/*/', $user['avatar']))
            {
                $avatarInfo = Util::wxImgUploadOss($user_info['headimgurl'], 'uploads/avatar');
                if ($avatarInfo['success'])
                {
                    $token_info['avatar'] = $avatarInfo['url'];
                    Cache::rm('userinfo:' . $uid);
                    // 更新档案信息
                    $recordModel = new Recode();
                    $rid = $recordModel->where('uid', $uid)->where('isself', 1)->value('id');
                    $recordModel->UpdateData($uid, $rid, ['avatar' => $avatarInfo['url']]);
                }
            }

            $UserModel->save($token_info, ['id' => $uid]);
        }
        catch (Exception $e)
        {
            Log::write('用户登录失败：' . $e->getMessage() . '【' . $e->getFile() . $e->getLine() . '】', 'error');
            return $this->msg = '用户登录失败';
        }
        $userInfo = $UserModel->getUserinfo(['id' => $uid]);
        //检测用户准了号
        $UserModel->check_uen($userInfo);

        $userPush = (new UserPush())->getUserRegid($uid);
        $calendarBirthdayStatus = (int)($userPush['calendar_birthday_status'] ?? 0);
        $recodePrivateStatus = (int)($userPush['recode_private_status'] ?? 0);

        $isLoginRecord = (int)($userInfo['is_login_record'] ?? 1);  // 是否记录设备登陆历史 1=>记录，2=>不记录
        // 记录登陆设备日志
        Util::loginRecordSave($uid, $this->loginType, $isLoginRecord, $this->request->ip());

        $distinctId = $this->request->header('distinct-id', '');
        if ($distinctId)
        {
            // 神策打点，用户关联
            SensorsData::signup($uid, $distinctId);
        }

        if (isset($userInfo['young_pwd']))
        {
            $isYoung = $userInfo['young_pwd'] ? true : false;
        } else
        {
            $isYoung = false;
        }

        return [
            'id' => $uid,
            'has_self_recode' => (int)$userInfo['has_self_recode'],
            'user_access_token' => (new Aes())->encryptWithOpenssl($token_info['token'] . '||' . $uid),
            'uc_id' => $userInfo['uc_id'],
            'calendar_birthday_status' => $calendarBirthdayStatus ?: 1,   // 在天象日历中展示生日信息
            'recode_private_status' => $recodePrivateStatus ?: 1,   // 在星盘中展示档案详细信息
            'has_bind_tel' => (($userInfo['tel'] ?? '') || ($userInfo['temporary_tel'] ?? '')) ? 1 : 0,
            'temporary_tel' => (bool)($userInfo['temporary_tel'] ?? ''),
            'is_register' => false,
            'is_young_model' => $isYoung ? 1 : 2,
        ];
    }

    /**
     * 获取用户token信息
     * @param $user_info
     * @return array
     */
    private function getUserTokenInfo($user_info)
    {
        // 生成token  和 token到期时间
        $token = IAuth::setAppLoginToken($user_info['unionid']);
        $data = [
            'token' => $token,
            'token_expire' => strtotime('+' . config('app.login_time_out_day') . ' days'),
        ];
        return $data;
    }

    /**
     * 记录用户的登陆日志
     * @param $uid
     * @param $type 1=>微信，2=>qq,3=>微博 4=>手机号
     * @return bool
     */
    private function LoginLog($uid, $type)
    {
        $time = $this->request->time();
        $ip = $this->request->ip();
        // 记录登陆日志  改成异步操作
        $insert = [
            'uid' => $uid,
            'login_time' => $time,
            'login_type' => $type,
            'login_ip' => $ip,
        ];
        try
        {
            (new AppLoginLog())->SaveData($insert);
        }
        catch (Exception $e)
        {
            Log::write('记录用户登录失败：' . $e->getMessage() . '【' . $e->getFile() . $e->getLine() . '】', 'error');
            return false;
        }

        return true;
    }

    /**
     * 获取手机验证码
     * @return Json
     * @throws Exception
     */
    public function getCode()
    {
        $type = input('param.type/d', 0);
        $tel = input('param.tel/s', '');
        $alidayu = (new AppConfig())->configInfo('alidayu');
        $codeConfig = $alidayu['login_code'];
        switch ($type)
        {
            case 1: // 登陆
                // 检验手机号是否注册过，注册过通过
                if (!$tel || !preg_match("/^1[3456789]\d{9}$/", $tel))
                {
                    return show(config('code.error'), '手机号错误');
                }
                /**
                 * 过滤非法获取验证码请求，直接返回发送成功
                 */
                if (Util::isIllegalRequest(true))
                {
                    return show(config('code.success'), '发送成功');
                }
                // try
                // {
                //     (new User)->where(['tel' => $tel])->findOrFail();
                // }
                // catch (Exception $e)
                // {
                //     return show(409, '手机号未注册，请用三方登录');
                // }
                break;
            case 2: // 绑定手机号
                // 检验手机号是否注册过，未注册过通过
                if (!$tel || !preg_match("/^1[3456789]\d{9}$/", $tel))
                {
                    return show(config('code.error'), '手机号错误');
                }
                if (!$this->user)
                {
                    return show(config('code.denied'), '登录失效');
                }
                try
                {
                    $user = (new User)->where(['tel' => $tel])->findOrFail();
                }
                catch (Exception $e)
                {
                    $user = [];
                }
                if ($user)
                {
                    return show(config('code.error'), '此手机号已注册，请使用此手机号登录或注销此手机号重新绑定');
                }
                break;
            case 3: // 注销
                // 服务器获取手机号，若有则通过
                $codeConfig = $alidayu['logout_code'];
                if (!$this->user)
                {
                    return show(config('code.denied'), '登录失效');
                }
                if ($this->user['tel'])
                {
                    $tel = $this->user['tel'];
                } else
                {
                    return show(config('code.error'), '您未绑定手机号');
                }
                break;
            default:
                return show(config('code.error'), '未知的登录方式');
        }

        $time = time();// 当前时间  有效时间暂定为十分钟 600s

        $expire = 600;

        $ip = $this->request->ip();

        try
        {
            $data = (new AppTelLogin())->where(['tel' => (string)$tel, 'expire_time' => ['>', $time]])->findOrFail()->toArray();
        }
        catch (Exception $e)
        {
            $data = [];
        }
        // 十分钟内无已有验证码
        if (empty($data))
        {
            $code = random_int(100000, 999999);// 六位验证码

            $expire_time = $time + $expire;
            try
            {
                Db::table('app_tel_login')->insertGetId([
                    'tel' => "$tel",
                    'create_time' => $time,
                    'expire_time' => $expire_time,
                    'code' => $code,
                ]);
            }
            catch (Exception $e)
            {
                Log::write('获取手机验证码失败：' . $e->getMessage() . '【' . $e->getFile() . $e->getLine() . '】', 'error');
                return show(config('code.error'), '发送失败');
            }

        } else
        {
            $code = $data['code'];
        }
        $result = Alidayu::getInstance()->sendSms($tel, $code, $codeConfig);
        if (!$result)
        {
            return show(config('code.error'), '发送失败');
        }
        Cache::set($this->user['id'] . 'logout', $code, 600);
        // 记录短信日志
        model('log')->save([
            'tel' => "$tel",
            'ip' => $ip,
            'create_time' => $time,
            'type' => $type,
        ]);
        return show(config('code.success'), '发送成功');
    }
}
